/*
 * Copyright 2006 JTech Network, Sweden
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package se.jtech.ant.xpath;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.w3c.dom.Document;

import se.jtech.xml.ConfigurableNamespaceContext;

/**
 * An Ant task that parses an XML file and then evaluates a set of XPath
 * expressions on the XML file. The task fails if any XPath expression does not
 * evaluate to <code>true</code>.
 * 
 * @author Claes Buckwalter (claes@jtech.se)
 */
public final class XPathEvaluatorTask extends Task {

    private File xmlFile;

    private ConfigurableNamespaceContext namespaceContext = null;

    private List xpaths = null;

    /**
     * Sets the XML file to process by this task.
     * 
     * @param xmlFile
     */
    public void setXmlFile(File xmlFile) {
        this.xmlFile = xmlFile;
        log("Set XML file: " + xmlFile.getAbsolutePath());
    }

    /**
     * Adds an XPath to be evaluated by this task.
     * 
     * @param xpath
     */
    public synchronized void addConfiguredXpath(Xpath xpath) {
        if (xpaths == null) {
            xpaths = new ArrayList();
        }
        xpaths.add(xpath);
    }

    /**
     * Adds a namespace used by the XML file and XPaths processed by this task.
     * 
     * @param namespace
     */
    public synchronized void addConfiguredNamespace(Namespace namespace) {
        if (namespaceContext == null) {
            namespaceContext = new ConfigurableNamespaceContext();
        }
        namespaceContext
                .addNamespace(namespace.getUri(), namespace.getPrefix());
        log("Added namespace: uri='" + namespace.getUri() + "'; prefix='"
                + namespace.getPrefix() + "'");
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.tools.ant.Task#execute()
     */
    public void execute() throws BuildException {
        // Parse XML file
        final DocumentBuilderFactory docFactory = DocumentBuilderFactory
                .newInstance();
        docFactory.setValidating(false);
        docFactory.setNamespaceAware(true);
        final DocumentBuilder builder;
        final Document xmlDoc;
        try {
            builder = docFactory.newDocumentBuilder();
            xmlDoc = builder.parse(xmlFile);
        } catch (Exception e) {
            throw new BuildException("Could not parse XML file '"
                    + xmlFile.getAbsolutePath() + "'.", e);
        }
        log("Parsed XML file: " + xmlFile.getAbsolutePath());
        // Evaluate XPaths
        final XPathFactory xpathFactory = XPathFactory.newInstance();
        log("Evaluating " + xpaths.size() + " XPath(s)...");
        for (Iterator i = xpaths.iterator(); i.hasNext();) {
            final Xpath xpath = (Xpath) i.next();
            final XPath xp = xpathFactory.newXPath();
            xp.setNamespaceContext(namespaceContext);
            final Boolean valid;
            log("Evaluting XPath '" + xpath + "'...");
            try {
                valid = (Boolean) xp.evaluate(xpath.getExpression(), xmlDoc,
                        XPathConstants.BOOLEAN);
            } catch (XPathExpressionException e) {
                throw new BuildException("Could not evaluate XPath '" + xpath
                        + "'.", e);
            }
            if (!valid.booleanValue()) {
                throw new BuildException("XPath '" + xpath
                        + "' did not evaluate to 'true'.");
            }
        }

    }

}
